module Settings

using ..FLEXINVERT: MAX_PATH_LEN, MAX_NAME_LEN

# Port of mod_settings: Files and Config types and settings readers

Base.@kwdef mutable struct Files
    path_obs::String       = ""
    path_prior::String     = ""
    path_output::String    = ""
    path_flexpart::String  = ""
    path_flexncdf::String  = ""
    file_bg::String        = ""
    suffix::String         = ""
    file_log::String       = "log.txt"
    file_orog::String      = ""
    varname_orog::String   = ""
    lonname_orog::String   = ""
    latname_orog::String   = ""
    file_regions::String   = ""
    varname_regs::String   = ""
    lonname_regs::String   = ""
    latname_regs::String   = ""
    file_lsm::String       = ""
    varname_lsm::String    = ""
    lonname_lsm::String    = ""
    latname_lsm::String    = ""
    filename_nee::String   = ""
    varname_nee::String    = ""
    lonname_nee::String    = ""
    latname_nee::String    = ""
    timename_nee::String   = ""
    filenest_nee::String   = ""
    varnest_nee::String    = ""
    lonnest_nee::String    = ""
    latnest_nee::String    = ""
    timenest_nee::String   = ""
    filename_ff::String    = ""
    varname_ff::String     = ""
    lonname_ff::String     = ""
    latname_ff::String     = ""
    timename_ff::String    = ""
    filenest_ff::String    = ""
    varnest_ff::String     = ""
    lonnest_ff::String     = ""
    latnest_ff::String     = ""
    timenest_ff::String    = ""
    filename_ocn::String   = ""
    varname_ocn::String    = ""
    lonname_ocn::String    = ""
    latname_ocn::String    = ""
    timename_ocn::String   = ""
    filenest_ocn::String   = ""
    varnest_ocn::String    = ""
    lonnest_ocn::String    = ""
    latnest_ocn::String    = ""
    timenest_ocn::String   = ""
    filename_flx::String   = ""
    varname_flx::String    = ""
    lonname_flx::String    = ""
    latname_flx::String    = ""
    timename_flx::String   = ""
    filenest_flx::String   = ""
    varnest_flx::String    = ""
    lonnest_flx::String    = ""
    latnest_flx::String    = ""
    timenest_flx::String   = ""
    path_initconc::String  = ""
    file_initconc::String  = ""
    varname_init::String   = ""
    init_type::Int         = 0
    file_recept::String    = ""
end

Base.@kwdef mutable struct Config
    spec::String         = "co2"           # 'co2' or 'ghg'
    method::String       = "congrad"       # 'congrad'|'m1qn3'|'analytic'|'mcmc'
    datei::Int           = 0               # yyyymmdd
    datef::Int           = 0               # yyyymmdd
    coeff::Float64       = 1.0
    molarmass::Float64   = 44.0
    nested::Bool         = false
    w_edge_lon::Float64  = 0.0
    s_edge_lat::Float64  = 0.0
    e_edge_lon::Float64  = 0.0
    n_edge_lat::Float64  = 0.0
    xres::Float64        = 1.0
    yres::Float64        = 1.0
    regions::Bool        = false
    nstep_nee::Int       = 0
    nstep_nee_reg::Int   = 0
    nstep_ff::Int        = 0
    nstep_ff_reg::Int    = 0
    nstep_ocn::Int       = 0
    nstep_flx::Int       = 0
    coeff_ff::Float64    = 1.0
    coeff_ff_reg::Float64= 1.0
    statres::Float64     = 1.0
    statres_hr::Float64  = 24.0
    measerr::Float64     = 0.0
    cinierr::Float64     = 0.0
    flxerr::Float64      = 0.0
    ffferr::Float64      = 0.0
    flxerr_ll::Float64   = 0.0
    sigma_land::Float64  = 0.0 # meters
    sigma_ocean::Float64 = 0.0 # meters
    sigmatime::Float64   = 0.0
    globerr::Float64     = 0.0
    trunc::Float64       = 0.0
    opt_cini::Bool       = false
    lognormal::Bool      = false
    inc_ocean::Bool      = true
    average_fp::Bool     = false
    restart::Int         = 0
    maxiter::Int         = 100
    nycini::Int          = 0
    nzcini::Int          = 0
    cini_lat::Vector{Float64} = Float64[]
    cini_alt::Vector{Float64} = Float64[]
    cinires::Int         = 1
    fortran_output_path::String = ""

    # MCMC-specific configuration parameters
    mcmc_samples::Int    = 10000          # Total MCMC samples
    mcmc_burnin::Int     = 2000           # Burn-in samples
    mcmc_thin::Int       = 1              # Thinning interval
    mcmc_chains::Int     = 4              # Number of parallel chains
    mcmc_proposal::String = "pcn"         # 'mh'|'pcn'|'mala'|'block'
    mcmc_step_size::Float64 = 0.1         # Base step size
    mcmc_pcn_beta::Float64 = 0.1          # pCN beta parameter (0 < β < 1)
    mcmc_mala_step::Float64 = 0.01        # MALA-specific step size
    mcmc_adapt_step::Bool = true          # Adapt step size during burn-in
    mcmc_target_accept::Float64 = 0.234   # Target acceptance rate
    mcmc_adapt_window::Int = 50           # Adaptation window size
    mcmc_diagnostics::Bool = true         # Compute convergence diagnostics
    mcmc_save_samples::Bool = true        # Save individual samples
    mcmc_diagnostic_freq::Int = 100       # Diagnostic computation frequency
    mcmc_sample_hyperparams::Bool = false # Sample hyperparameters hierarchically
    mcmc_hyper_freq::Int = 10             # Hyperparameter update frequency
    mcmc_likelihood::String = "gaussian"  # 'gaussian'|'student_t'
    mcmc_student_nu::Float64 = 4.0        # Student-t degrees of freedom
end

"""
    read_file_settings(path::AbstractString) -> Files

Parses the file settings (Fortran: read_file_settings). Supports lines of the form
`key: value` and ignores comments starting with `#`.
"""
function read_file_settings(path::AbstractString)
    files = Files()
    open(path, "r") do io
        for raw in eachline(io)
            line = strip(raw)
            isempty(line) && continue
            startswith(line, "#") && continue
            if occursin(":", line)
                key, val = split(line, ":"; limit=2)
                key = strip(key); val = strip(val)
                if hasproperty(files, Symbol(key))
                    sym = Symbol(key)
                    # Handle type conversion for non-string fields
                    if sym === :init_type
                        setproperty!(files, sym, parse(Int, val))
                    else
                        setproperty!(files, sym, val)
                    end
                end
            end
        end
    end
    return files
end

"""
    read_config_settings(path::AbstractString) -> Config

Parses the config settings (Fortran: read_config_settings). Handles numeric, logical,
and list fields where applicable. Performs unit conversions and sanity checks similar
to the Fortran implementation.
"""
function read_config_settings(path::AbstractString)
    cfg = Config()
    tmp_lat = Float64[]
    tmp_alt = Float64[]
    open(path, "r") do io
        for raw in eachline(io)
            line = strip(raw)
            isempty(line) && continue
            startswith(line, "#") && continue
            if occursin(":", line)
                key, val = split(line, ":"; limit=2)
                key = strip(key); val = strip(val)
                sym = Symbol(key)
                if sym in (:spec, :method, :mcmc_proposal, :mcmc_likelihood)
                    setproperty!(cfg, sym, lowercase(strip(val)))
                elseif sym in (:nested, :regions, :opt_cini, :lognormal, :inc_ocean, :average_fp,
                               :mcmc_adapt_step, :mcmc_diagnostics, :mcmc_save_samples, :mcmc_sample_hyperparams)
                    v = replace(lowercase(strip(val)), "."=>"")
                    setproperty!(cfg, sym, v in ("true","t","1","yes","y"))
                elseif sym in (:datei, :datef, :nstep_nee, :nstep_nee_reg, :nstep_ff, :nstep_ff_reg,
                               :nstep_ocn, :nstep_flx, :restart, :maxiter, :mcmc_samples, :mcmc_burnin,
                               :mcmc_thin, :mcmc_chains, :mcmc_adapt_window, :mcmc_diagnostic_freq, :mcmc_hyper_freq)
                    setproperty!(cfg, sym, parse(Int, val))
                elseif sym === :cinires
                    # cinires can have decimal point but needs to be converted to Int
                    setproperty!(cfg, sym, Int(round(parse(Float64, val))))
                elseif sym in (:coeff, :molarmass, :w_edge_lon, :s_edge_lat, :e_edge_lon, :n_edge_lat, :xres, :yres,
                               :coeff_ff, :coeff_ff_reg, :statres, :statres_hr, :measerr, :cinierr,
                               :flxerr, :ffferr, :flxerr_ll, :sigma_land, :sigma_ocean, :sigmatime, :globerr, :trunc,
                               :mcmc_step_size, :mcmc_pcn_beta, :mcmc_mala_step, :mcmc_target_accept, :mcmc_student_nu)
                    setproperty!(cfg, sym, parse(Float64, val))
                elseif sym === :fortran_output_path
                    setproperty!(cfg, sym, strip(val))
                elseif sym in (:cini_lat, :cini_alt)
                    arr = [parse(Float64, strip(s)) for s in split(val, ",") if !isempty(strip(s))]
                    if sym === :cini_lat
                        tmp_lat = arr
                    else
                        tmp_alt = arr
                    end
                else
                    # Ignore unknown keys to be permissive
                end
            end
        end
    end

    # Complete cini vectors
    if !isempty(tmp_lat); cfg.cini_lat = tmp_lat; cfg.nycini = length(tmp_lat); end
    if !isempty(tmp_alt); cfg.cini_alt = tmp_alt; cfg.nzcini = length(tmp_alt); end

    # Convert km -> m for correlation lengths, mirroring Fortran behavior
    cfg.sigma_land *= 1000.0
    cfg.sigma_ocean *= 1000.0

    # Guard rails similar to Fortran checks
    if cfg.opt_cini && cfg.spec == "co2"
        @warn "opt_cini not supported for CO2; disabling"
        cfg.opt_cini = false
    end
    if cfg.lognormal
        @info "lognormal inversion forces method=m1qn3"
        cfg.method = "m1qn3"
        if cfg.spec == "co2"
            @warn "lognormal not allowed for CO2; switching to normal"
            cfg.lognormal = false
        end
    end
    if cfg.spec == "co2" && cfg.inc_ocean
        @warn "For CO2, ocean fluxes are not optimized; inc_ocean=false"
        cfg.inc_ocean = false
    end

    for (k, cond) in (
        :measerr => cfg.measerr < 0,
        :cinierr => cfg.cinierr < 0,
        :flxerr => cfg.flxerr < 0,
        :flxerr_ll => cfg.flxerr_ll < 0,
        :sigma_land => cfg.sigma_land < 0,
        :sigma_ocean => cfg.sigma_ocean < 0,
        :sigmatime => cfg.sigmatime < 0,
        :trunc => cfg.trunc < 0,
    )
        cond && error("Invalid value for $(k)")
    end

    # MCMC-specific validation
    if cfg.method == "mcmc"
        # Validate MCMC parameters
        cfg.mcmc_samples <= 0 && error("mcmc_samples must be positive")
        cfg.mcmc_burnin < 0 && error("mcmc_burnin must be non-negative")
        cfg.mcmc_chains <= 0 && error("mcmc_chains must be positive")
        cfg.mcmc_thin <= 0 && error("mcmc_thin must be positive")

        # Validate step size parameters
        cfg.mcmc_step_size <= 0 && error("mcmc_step_size must be positive")
        !(0 < cfg.mcmc_pcn_beta < 1) && error("mcmc_pcn_beta must be between 0 and 1")
        cfg.mcmc_mala_step <= 0 && error("mcmc_mala_step must be positive")

        # Validate target acceptance rate
        !(0 < cfg.mcmc_target_accept < 1) && error("mcmc_target_accept must be between 0 and 1")

        # Validate proposal type
        cfg.mcmc_proposal ∉ ["mh", "pcn", "mala", "block"] &&
            error("mcmc_proposal must be one of: mh, pcn, mala, block")

        # Validate likelihood type
        cfg.mcmc_likelihood ∉ ["gaussian", "student_t"] &&
            error("mcmc_likelihood must be one of: gaussian, student_t")

        # Validate degrees of freedom for Student-t
        if cfg.mcmc_likelihood == "student_t" && cfg.mcmc_student_nu <= 0
            error("mcmc_student_nu must be positive for Student-t likelihood")
        end

        # Validate adaptation window
        cfg.mcmc_adapt_window <= 0 && error("mcmc_adapt_window must be positive")

        # Ensure burn-in is reasonable compared to total samples
        if cfg.mcmc_burnin >= cfg.mcmc_samples
            @warn "Burn-in period ($(cfg.mcmc_burnin)) >= total samples ($(cfg.mcmc_samples))"
        end

        @info "MCMC configuration validated successfully"
        @info "Proposal: $(cfg.mcmc_proposal), Samples: $(cfg.mcmc_samples), Chains: $(cfg.mcmc_chains)"
    end

    return cfg
end

end # module
